﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Reflection;
using System.Data;
using System.Data.Entity;
using System.Data.EntityClient;
using Model;


namespace Util
{
	public class DalUtil
	{
		private static readonly object mLock = new object();
        public static string StrPrefix = "zhaxEntities.";
        public static string StrConn = "name=zhaxEntities";

		/// <summary>
		/// GetEntities
		/// </summary>
		/// <returns>IShopEntities</returns>
		public static ObjectContext GetEntities()
		{
            return new zhaxEntities(StrConn);
            //return null;
		}

		/// <summary>
		/// Get Primary Key of the type
		/// </summary>
		/// <param name="entityType"></param>
		/// <returns>primary key of the object</returns>
		private static string GetPrimaryKey(Type entityType)
		{
			foreach (var prop in entityType.GetProperties())
			{
				var attr = prop.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).FirstOrDefault()
				 as EdmScalarPropertyAttribute;
				if (attr != null && attr.EntityKeyProperty)
					return prop.Name;
			}
			return null;
		}

		/// <summary>
		/// Add entity object into db
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="entity"></param>
		/// <returns>changedCount</returns>
		public static int Add<T>(T entity) where T : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						context.AddObject(typeof(T).Name, entity);
						changedCount = context.SaveChanges();
						if (changedCount > 0)
							context.AcceptAllChanges();
						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="entity"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static int Add<T>(T entity, ref object key) where T : EntityObject
        {
            int changedCount = 0;
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                try
                {
                    using (ObjectContext context = GetEntities())
                    {
                        Type type = typeof(T);
                        context.AddObject(typeof(T).Name, entity);
                        changedCount = context.SaveChanges();
                        if (changedCount > 0)
                            context.AcceptAllChanges();
                        scope.Complete();

                        string keyname = GetPrimaryKey(type);
                        PropertyInfo property = type.GetProperty(keyname);
                        key = property.GetValue(entity, null);
                    }
                }
                catch (Exception x)
                {
                    changedCount = 0;
                    throw x;
                }
            }
            return changedCount;
        }

		/// <summary>
		/// Add entity list to db
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="list"></param>
		/// <returns>changedCount</returns>
		public static int Add<T>(IList<T> list) where T : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						foreach (T item in list)
							context.AddObject(typeof(T).Name, item);
						changedCount = context.SaveChanges();
						if (changedCount > 0)
							context.AcceptAllChanges();
						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}

		/// <summary>
		/// Query single object by its primary key id
		/// </summary>
		/// <typeparam name="T">EntityObject</typeparam>
		/// <param name="id">parimary key value</param>
		/// <returns></returns>
		public static T GetSingle<T>(int id) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					string key = GetPrimaryKey(typeof(T));
					EntityKey entityKey = new EntityKey(StrPrefix + typeof(T).Name, key, id);
					var objResult = context.GetObjectByKey(entityKey);
					return objResult as T;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query single object by predicate
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public static T GetSingle<T>(Func<T, bool> predicate) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					var objectSet = context.CreateObjectSet<T>().Where(predicate);
					if (objectSet.Count() > 0)
						return objectSet.First();
					else
						return null;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query entity by predicate with single navigation attribute
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="includePath"></param>
		/// <returns></returns>
		public static T GetSingle<T>(Func<T, bool> predicate, string includePath) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					var objectQuery = context.CreateObjectSet<T>()
					 .Include(includePath)
					 .Where(predicate);
					if (objectQuery.Count() > 0)
						return objectQuery.First();
					else
						return null;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query entity by predicate with more than one navigation attributes
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="includePaths"></param>
		/// <returns></returns>
		public static T GetSingle<T>(Func<T, bool> predicate, string[] includePaths) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					var list = context.CreateObjectSet<T>().Where("1==1");
					foreach (var path in includePaths)
						list = list.Include(path);
					var returnValue = list.Where(predicate).ToList();
					if (returnValue.Count() > 0)
						return returnValue.First();
					else
						return null;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query objects by predicate
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = objectSet.Where(predicate).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// query set skip and take
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="skip">skiped record count</param>
		/// <param name="take">taking record count</param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, int skip, int take) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = objectSet.Where(predicate).Skip(skip).Take(take).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// QueryList with predicate and order
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="asce1"></param>
		/// <param name="order1"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, bool asce1, Func<T, object> order1) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = null;
					if (asce1)
						list = objectSet.Where(predicate).OrderBy(order1).ToList();
					else
						list = objectSet.Where(predicate).OrderByDescending(order1).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// QueryList with predicate and order
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="asce1"></param>
		/// <param name="order1"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, bool asce1, Func<T, object> order1, int skip, int take) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = null;
					if (asce1)
						list = objectSet.Where(predicate).OrderBy(order1).Skip(skip).Take(take).ToList();
					else
						list = objectSet.Where(predicate).OrderByDescending(order1).Skip(skip).Take(take).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// QuerySet with order1 skip and take
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="asce1">ascending or not</param>
		/// <param name="order1">order1 func</param>
		/// <param name="skip"></param>
		/// <param name="take"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, bool asce1, Func<T, object> order1, bool asce2, Func<T, object> order2, int skip, int take) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = null;
					if (asce1)
					{
						if (asce2)
							list = objectSet.Where(predicate).OrderBy(order1).ThenBy(order2).Skip(skip).Take(take).ToList();
						else
							list = objectSet.Where(predicate).OrderBy(order1).ThenByDescending(order2).Skip(skip).Take(take).ToList();
					}
					else
					{
						if (asce2)
							list = objectSet.Where(predicate).OrderByDescending(order1).ThenBy(order2).Skip(skip).Take(take).ToList();
						else
							list = objectSet.Where(predicate).OrderByDescending(order1).ThenByDescending(order2).Skip(skip).Take(take).ToList();
					}
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Get list with sort
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="dic"></param>
		/// <param name="skip"></param>
		/// <param name="take"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, IDictionary<Func<T, object>, bool> dic, int skip, int take) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					var list = objectSet.Where(predicate);
					IOrderedEnumerable<T> temp = null;
					bool isFirst = true;
					if (dic != null && dic.Count > 0)
					{
						foreach (var item in dic)
						{
							if (isFirst)
							{
								if (item.Value)
								{
									temp = list.OrderBy(item.Key);
								}
								else
								{
									temp = list.OrderByDescending(item.Key);
								}
								isFirst = !isFirst;
							}
							else
							{
								if (item.Value)
								{
									temp = temp.ThenBy(item.Key);
								}
								else
								{
									temp = temp.ThenByDescending(item.Key);
								}
							}
						}
					}
					if (temp != null)
					{
						list = temp.Skip(skip).Take(take);
					}
					else
					{
						list = list.Skip(skip).Take(take);
					}
					return list.ToList();
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query entities by predicate with single navigation attribute
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="includePath"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, string includePath) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					ObjectSet<T> objectSet = context.CreateObjectSet<T>();
					IList<T> list = objectSet.Include(includePath).Where(predicate).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query entities by predicate with more than one navigation attributes
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <param name="includePaths"></param>
		/// <returns></returns>
		public static IList<T> GetList<T>(Func<T, bool> predicate, string[] includePaths) where T : EntityObject
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					var list = context.CreateObjectSet<T>().Where("1==1");
					foreach (var path in includePaths)
						list = list.Include(path);
					return list.Where(predicate).ToList();
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Query entities by sql
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="sqlText"></param>
		/// <param name="parameters">SqlParameter will be ok</param>
		/// <returns></returns>
		public static IList<T> GetList<T>(string sqlText, params object[] parameters)
		{
			try
			{
				using (ObjectContext context = GetEntities())
				{
					IList<T> list = context.ExecuteStoreQuery<T>(sqlText, parameters).ToList();
					return list;
				}
			}
			catch (Exception x)
			{
				throw x;
			}
		}

		/// <summary>
		/// Update entity without navigation attribute
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="entity"></param>
		/// <returns>changedCount</returns>
		public static int Update<T>(T entity) where T : EntityObject
		{
			lock (mLock)
			{
				int changedCount = 0;
				Type type = typeof(T);

				using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
				{
					try
					{
						using (ObjectContext context = GetEntities())
						{
							string key = GetPrimaryKey(typeof(T));
							//获取实体的Id属性
							PropertyInfo property = type.GetProperty(key);
							object id = property.GetValue(entity, null);
							//根据Id获取上下文中的对应实体
							EntityKey entityKey = new EntityKey(StrPrefix + type.Name, key, id);
							var objResult = context.GetObjectByKey(entityKey);
							//更新实体属性
							if (objResult != null)
								context.ApplyCurrentValues<T>(type.Name, entity);
							changedCount = context.SaveChanges();
							if (changedCount > 0)
								context.AcceptAllChanges();
							scope.Complete();
						}
					}
					catch (Exception x)
					{
						changedCount = 0;
						throw x;
					}
				}
				return changedCount;
			}
		}

		/// <summary>
		/// Update entity with related infos in other tables
		/// </summary>
		/// <typeparam name="T1"></typeparam>
		/// <typeparam name="T2"></typeparam>
		/// <param name="entity"></param>
		/// <returns></returns>
		public static int Update<T1, T2>(T1 entity)
			where T1 : EntityObject
			where T2 : EntityObject
		{
			lock (mLock)
			{
				int changedCount = 0;
				Type type1 = typeof(T1);
				Type type2 = typeof(T2);
				string t1Key = GetPrimaryKey(type1);
				using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
				{
					try
					{
						using (ObjectContext context = GetEntities())
						{
							PropertyInfo property = type1.GetProperty(t1Key);
							object id = property.GetValue(entity, null);

							//根据软件Id建立EntityKey对象
							EntityKey entityKey = new EntityKey(StrPrefix + type1.Name, t1Key, id);
							//根据EntityKey查找对应对象
							T1 objT1 = context.GetObjectByKey(entityKey) as T1;
							//在上下文中更新当前对象
							if (objT1 != null)
								context.ApplyCurrentValues<T1>(type1.Name, entity);

							//获取外键属性
							PropertyInfo propertyInfo = type1.GetProperty(type2.Name);

							//在一对多关键时更新导航属性
							var T2List = propertyInfo.GetValue(entity, null)
							 as EntityCollection<T2>;
							if (T2List != null)
							{
								foreach (var obj in T2List.ToList())
								{
									var oldEntity = context.GetObjectByKey(obj.EntityKey);
									if (oldEntity != null)
										context.ApplyCurrentValues<T2>(type2.Name, obj);
								}
							}

							//在多对一，一对一关系时更新导航属性
							var objT2 = propertyInfo.GetValue(entity, null) as T2;
							if (objT2 != null)
							{
								var oldEntity = context.GetObjectByKey(objT2.EntityKey);
								if (oldEntity != null)
									context.ApplyCurrentValues<T2>(type2.Name, objT2);
							}
							changedCount = context.SaveChanges();
							if (changedCount > 0)
								context.AcceptAllChanges();

							scope.Complete();
						}
					}
					catch (Exception x)
					{
						changedCount = 0;
						throw x;
					}
				}
				return changedCount;
			}
		}

		/// <summary>
		/// Delete
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="id"></param>
		/// <returns></returns>
		public static int Delete<T>(int id) where T : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						string key = GetPrimaryKey(typeof(T));
						//建立EntityKey对象
						EntityKey entityKey = new EntityKey(StrPrefix + typeof(T).Name, key, id);
						//通过EntityKey找到实体
						var objResult = context.GetObjectByKey(entityKey);
						//若实体存在则删除实体
						if (objResult != null)
							context.DeleteObject(objResult);
						changedCount = context.SaveChanges();
						if (changedCount > 0)
							context.AcceptAllChanges();

						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}

		/// <summary>
		/// Delete
		/// </summary>
		/// <typeparam name="PKEntity"></typeparam>
		/// <typeparam name="FKEntity"></typeparam>
		/// <param name="id"></param>
		/// <returns></returns>
		public static int Delete<PKEntity, FKEntity>(int id)
			where PKEntity : EntityObject
			where FKEntity : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						string key = GetPrimaryKey(typeof(PKEntity));
						//根据软件Id建立EntityKey对象
						EntityKey entityKey = new EntityKey(StrPrefix + typeof(PKEntity).Name, key, id);
						//根据EntityKey查找对应对象
						PKEntity objResult = context.GetObjectByKey(entityKey) as PKEntity;
						//根据FKEntity加载导航属性
						PropertyInfo propertyInfo = typeof(PKEntity).GetProperty(typeof(FKEntity).Name);
						EntityCollection<FKEntity> FKEntityList = propertyInfo.GetValue(objResult, null)
						 as EntityCollection<FKEntity>;

						if (FKEntityList != null)
							FKEntityList.Load();

						if (objResult != null)
							context.DeleteObject(objResult);
						changedCount = context.SaveChanges();

						if (changedCount > 0)
							context.AcceptAllChanges();

						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}

		/// <summary>
		/// Delete
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public static int Delete<T>(Func<T, bool> predicate) where T : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						//根据输入的委托查找数据
						var list = context.CreateObjectSet<T>().Where(predicate);
						//若存在数据，删除有关数据
						if (list.Count() > 0)
							foreach (var obj in list)
								context.DeleteObject(obj);

						changedCount = context.SaveChanges();
						if (changedCount > 0)
							context.AcceptAllChanges();

						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}

		/// <summary>
		/// Delete
		/// </summary>
		/// <typeparam name="PKEntity"></typeparam>
		/// <typeparam name="FKEntity"></typeparam>
		/// <param name="predicate"></param>
		/// <returns></returns>
		public int Delete<PKEntity, FKEntity>(Func<PKEntity, bool> predicate)
			where PKEntity : EntityObject
			where FKEntity : EntityObject
		{
			int changedCount = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						//根据输入的委托查找数据
						var list = context.CreateObjectSet<PKEntity>().Where(predicate);
						//若数目大于0，删除有关数据
						if (list.Count() > 0)
						{
							foreach (var obj in list)
							{
								//在删除前加载其导航属性
								PropertyInfo propertyInfo = typeof(PKEntity).GetProperty(typeof(FKEntity).Name);
								EntityCollection<FKEntity> FKEntityList = propertyInfo.GetValue(obj, null)
								 as EntityCollection<FKEntity>;
								if (FKEntityList.Count > 0)
									FKEntityList.Load();

								context.DeleteObject(obj);
							}
						}

						changedCount = context.SaveChanges();

						if (changedCount > 0)
							context.AcceptAllChanges();

						scope.Complete();
					}
				}
				catch (Exception x)
				{
					changedCount = 0;
					throw x;
				}
			}
			return changedCount;
		}

		/// <summary>
		/// Exec sql
		/// </summary>
		/// <param name="sqlText"></param>
		/// <param name="parameters">SqlParameter will be ok</param>
		/// <returns>int</returns>
		public static int ExecNonQuery(string sqlText, params object[] parameters)
		{
			int affected = 0;
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						affected = context.ExecuteStoreCommand(sqlText, parameters);
						scope.Complete();
					}
				}
				catch (Exception x)
				{
					affected = 0;
					throw x;
				}
			}
			return affected;
		}

		public static IList<T> ExecuteFunction<T>(string spName, params ObjectParameter[] parameters) where T : class
		{
			using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
			{
				try
				{
					using (ObjectContext context = GetEntities())
					{
						return context.ExecuteFunction<T>(spName, parameters).ToList();
					}
				}
				catch (Exception x)
				{
					throw x;
				}
			}
		}
	}
}